/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023.
 * Description: Support logging after appraise integrity measurement failure
 * Author: zhuyan & xiongshenglan
 * Create: 2023-05-04
 */
#include <linux/seq_file.h>
#include <linux/kbox/kbox.h>
#include <linux/integrity.h>
#include <linux/mm.h>

#define IMA_APPRAISE_KBOX_SIZE 65536
#define IMA_APPRAISE_LOG_SIZE 512
#define CMDLINE_MAXSIZE 1024

struct dentry *ima_appraise_faillog;
bool ima_appraise_faillog_enable;
static DEFINE_MUTEX(ima_appraise_faillog_mutex);
static bool ima_appraise_region_inited;
static int ima_appraise_region_id;

static int ima_appraise_init_kbox(void)
{
	int tmpid;
	struct kbox_region appraise_region = {
		.name = "ima_appraise",
		.size = IMA_APPRAISE_KBOX_SIZE,
	};

	tmpid = kbox_register_region(appraise_region);
	if (tmpid < 0) {
		pr_err("register ima_appraise region failed\n");
		return tmpid;
	}

	ima_appraise_region_id = tmpid;
	ima_appraise_region_inited = true;
	return 0;
}

static ssize_t ima_appraise_faillog_write(struct file *filp, const char __user *buf,
					    size_t datalen, loff_t *ppos)
{
	ssize_t result;
	int faillog_enable;

	if (*ppos != 0)
		return -EINVAL;

	result = kstrtou32_from_user(buf, datalen, 10, &faillog_enable);
	if (result < 0)
		return result;

	if (faillog_enable != true && faillog_enable != false) {
		pr_err("IMA: invalid appraise_faillog value\n");
		return -EINVAL;
	}

	result = mutex_lock_interruptible(&ima_appraise_faillog_mutex);
	if (result < 0)
		return result;

	if (faillog_enable == true && !ima_appraise_faillog_enable) {
		pr_info("IMA: faillog_enable is set to %d. Enable appraise faillog\n", faillog_enable);
		ima_appraise_faillog_enable = true;
	} else if (faillog_enable == false && ima_appraise_faillog_enable) {
		pr_info("IMA: faillog_enable is set to %d. Disable appraise faillog\n", faillog_enable);
		ima_appraise_faillog_enable = false;
	}

	mutex_unlock(&ima_appraise_faillog_mutex);
	if (ima_appraise_faillog_enable && !ima_appraise_region_inited) {
		result = ima_appraise_init_kbox();
		if (result < 0)
			return result;
	}

	result = datalen;

	return result;
}

static int ima_appraise_faillog_show(struct seq_file *m, void *v)
{
	seq_printf(m, "%d\n", ima_appraise_faillog_enable);
	return 0;
}

static int ima_appraise_faillog_open(struct inode *inode, struct file *file)
{
	return single_open(file, ima_appraise_faillog_show, inode);
}

const struct file_operations ima_appraise_faillog_ops = {
	.open = ima_appraise_faillog_open,
	.read = seq_read,
	.write = ima_appraise_faillog_write,
	.release = single_release,
	.llseek = generic_file_llseek,
};

void ima_appraise_log(const char *fmt, ...)
{
	int text_len;
	va_list args;
	char logbuf[IMA_APPRAISE_LOG_SIZE] = {0};

	va_start(args, fmt);
	text_len = vscnprintf(logbuf, IMA_APPRAISE_LOG_SIZE, fmt, args);
	va_end(args);

	kbox_write(ima_appraise_region_id, logbuf, text_len);
}

void ima_appraise_faillog_handle(enum integrity_status status, const char *cause,
			   const unsigned char *filename)
{
	static unsigned long faillog_id;
	char cmd[CMDLINE_MAXSIZE] = {0};

	(void) get_cmdline(current, cmd, sizeof(cmd) - 1);

	if (status != INTEGRITY_PASS && ima_appraise_faillog_enable) {
		faillog_id++;
		ima_appraise_log("%lu\t%s\t%s\t%d\t%s\t%s\n", faillog_id, cause, filename,
				 current->pid, current->comm, cmd);
	}
}
